﻿/*
 * ShpDbReader
 * 
 * initial implementation : 20.12.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections;
using System.IO;
using System.Data;
using System.Diagnostics;
using System.Globalization;

using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using System.Data.Common;


namespace Petra.Gis.DataServices.SHP
{

	/// <summary>
	/// Creates DataReader that can be used to enumerate through an ESRI shape file.
	/// </summary>
	/// <remarks>	
	/// To create a ShapefileDataReader, use the static methods on the Shapefile class.
	/// </remarks>
	public class ShpDbReader : SpatialDbDataReader
	{

		bool _open = false;
		DbaseField[] _dbaseFields;
		ArrayList _columnValues;
		DbaseFileReader _dbfReader;
		ShpGeometryReader _shpReader;
		IEnumerator _dbfEnumerator;
		IEnumerator _shpEnumerator;
		ShapefileHeader _shpHeader;
		DbaseHeader _dbfHeader;
		bool _moreRecords = false;
		int _recordCount=0;

    //uint _currShpRecId;
    BoundingBox _coarseSpatialFilter;

    GeometryBase geometry = null;

    ///// <summary>
    ///// Return geometry feature of the shapefile.
    ///// </summary>
    //public Geometry Geometry
    //{
    //    get { return geometry; }         
    //}




    //public ShpDbReader(string filename)
    //  : this(filename, null, null) { }





    //public ShpDbReader(string filename, BoundingBox coarseSpatialFilter)
    //  : this(filename, null, coarseSpatialFilter) { }



		/// <summary>
		/// Initializes a new instance of the ShapefileDataReader class.
		/// </summary>
		/// <param name="filename">The shapefile to read (minus the .shp extension)</param>
		///<param name="geometryFactory">The GeometryFactory to use.</param>
    public ShpDbReader(string filename, System.Text.Encoding forceThisTextEncoding, BoundingBox coarseSpatialFilter)
		{
      if (filename == null || filename == "")
				throw new ArgumentNullException("filename");

			_open = true;			

			if (filename.ToLower().EndsWith(".shp"))
				filename = filename.ToLower().Replace(".shp", String.Empty);
			
			 _dbfReader = DbaseFileReader.Create(filename + ".dbf");

      // if connection has specific CodePage then
      // force over this given by header
      if (forceThisTextEncoding != null)
        _dbfReader.TextEncoding = forceThisTextEncoding;


			_shpReader = ShpGeometryReader.Create(filename + ".shp");

      _dbfHeader = _dbfReader.DbfHeader;
			_recordCount = _dbfHeader.NumRecords;

      //_shpReader.Header.
			
			// copy dbase fields to our own array. Insert into the first position, the shape column
			_dbaseFields = new DbaseField[_dbfHeader.Fields.Length];
			//_dbaseFields[0] = DbaseField.ShapeField();
			for(int i=0; i < _dbfHeader.Fields.Length; i++)
				_dbaseFields[i] = _dbfHeader.Fields[i];
			
			_shpHeader = _shpReader.Header;			
			_dbfEnumerator = _dbfReader.GetEnumerator();
			_shpEnumerator = _shpReader.GetEnumerator();


      _coarseSpatialFilter = coarseSpatialFilter;

			_moreRecords = true;
		}






    public override bool HasRows
    {
      get { return _recordCount > 0; }
    }





		/// <summary>
		/// Closes it
		/// </summary>
		public override void Close()
		{
			_open = false;
      //_shpReader.
		}




		/// <summary>
		/// Advances the DbDataReader to the next record.
		/// </summary>
		/// <returns>true if there are more rows; otherwise, false.</returns>
		public override bool Read()
		{

      //primitive spatial filter

      bool read = true;

      while (read && _moreRecords)
      {

        // move next moves to first record if
        // no record was previously reader
        bool moreDbfRecords = _dbfEnumerator.MoveNext();
        bool moreShpRecords = _shpEnumerator.MoveNext();

        _moreRecords = moreDbfRecords && moreShpRecords;

        // read record from shp&dbf
        // get current shape 
        geometry = (GeometryBase)_shpEnumerator.Current;

        // if shape outside of spatial filter
        // try read next
        if (_coarseSpatialFilter != null)
        {
          if (geometry.BoundingBox.Intersects(_coarseSpatialFilter))
            read = false;
        }
        else
          read = false;

        // get current dbase record
        _columnValues = (ArrayList)_dbfEnumerator.Current;

        //add geometry as first
        _columnValues.Insert(0, geometry);
        
      }

      return _moreRecords; // moreDbfRecords && moreShpRecords;

		}







		/// <summary>
		/// Gets a value indicating whether the data reader is closed.
		/// </summary>
		/// <value>true if the data reader is closed; otherwise, false.</value>
		/// <remarks>IsClosed and RecordsAffected are the only properties that you can call after the IDataReader is closed.</remarks>
    public override bool IsClosed
		{
			get
			{
				return !_open;
			}
		}





		/// <summary>
		/// Always return a value of zero since nesting is not supported.
		/// </summary>
		/// <value>The level of nesting.</value>
		/// <remarks>The outermost table has a depth of zero.</remarks>
    public override int Depth
		{
			get
			{
				return 0;
			}
		}





		/// <summary>
		/// Gets the numbers of records in the Shapefile.
		/// </summary>
    public override int RecordsAffected
		{
			get
			{
				return _recordCount;
			}
		}		






		/// <summary>
		/// 
		/// </summary>
    protected override void Dispose(bool disposing)
		{
			//throw new NotSupportedException();
		}
		





		public override int GetInt32(int i)
		{
			return (int)_columnValues[i+1];
		}






    /// <summary>
    /// 
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
		public override object GetValue(int i)
		{
      //first field is geometry
  	  return _columnValues[i];
		}







		public override bool IsDBNull(int i)
		{
			  return GetValue(i) == null;
		}





 
		public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
		{
			return 0;
		}





    public override byte GetByte(int i)
		{
			return 0;
		}






    public override System.Type GetFieldType(int i)
		{
      if (i == 0)
        return typeof(GeometryBase);
      else
			  return _dbaseFields[i-1].DataType;
		}





		public override decimal GetDecimal(int i)
		{
			return 0;
		}






    public override int GetValues(object[] values)
		{
			int i = 0;

      //values[0] = geometry; 
			for(i = 0; i < values.Length; i++)
				values[i] = _columnValues[i];
			return i;
		}





		public override string GetName(int i)
		{
      if (i == 0)
        return "Geometry";
      else
        return this._dbaseFields[i-1].Name;
		}






		public override long GetInt64(int i)
		{
			return (long)_columnValues[i];
		}






		public override double GetDouble(int i)
		{
			  return (double)_columnValues[i];
		}





		public override bool GetBoolean(int i)
		{
  			return (bool)_columnValues[i];
		}






		public override System.Guid GetGuid(int i)
		{
  			return new System.Guid();
		}






    public override System.DateTime GetDateTime(int i)
		{
        return new System.DateTime();
		}






		public override int GetOrdinal(string name)
		{

      if (name.ToLower() == "shpgeometry")
        return 0;
      else
      {
        for (int i = 0; i < _dbfReader.DbfHeader.NumFields; i++)
          if (0 == CultureAwareCompare(_dbfReader.DbfHeader.Fields[i].Name, name))
            return i + 1;
        // Throw an exception if the ordinal cannot be found.
        throw new IndexOutOfRangeException("Could not find specified column in results.");
      }
		}






		public override string GetDataTypeName(int i)
		{
      if (i == 0)
        return geometry.ToString();
      else
			  return _dbaseFields[i-1].DataType.ToString();
		}






    public override float GetFloat(int i)
		{
  			return (float)_columnValues[i];
		}





    //public override System.Data.IDataReader GetData(int i)
    //{
    //  /*
    //  * The sample code does not support this method. Normally,
    //  * this would be used to expose nested tables and
    //  * other hierarchical data.
    //  */
    //  throw new NotSupportedException("GetData not supported.");
    //}






		public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
		{

        //HACK:
        string str = _columnValues[i].ToString();
        str.CopyTo((int)fieldoffset, buffer, 0, length);
        return length;
		}






		public override string GetString(int i)
		{
      return _columnValues[i].ToString();
		}






		public override char GetChar(int i)
		{
        return (char)_columnValues[i];
		}





		public override short GetInt16(int i)
		{
  			return (short)_columnValues[i];
		}






		public override object this[string name]
		{
      get
      {
        // add one allow for the fact the first column is the WKB geometry
        return _columnValues[this.GetOrdinal(name)];
      }
		}






		public override object this[int i]
		{
			get
			{
				return _columnValues[i];
			}
		}

 



    public override int FieldCount
		{
			get
			{
				return _dbaseFields.Length + 1;
			}
		}






		public override System.Collections.IEnumerator GetEnumerator()
		{


      while(this.Read())
        yield return new RowStructure(_dbaseFields, _columnValues);
			
		}




    public override DataTable GetSchemaTable()
    {
      //$
      //return _sqlDr.GetSchemaTable();
      throw new NotImplementedException();
    }



    ///// <summary>
    ///// Gets the header for the Shapefile.
    ///// </summary>
    //public ShapefileHeader ShapeHeader
    //{
    //  get
    //  {
    //    return _shpHeader;
    //  }
    //}

    ///// <summary>
    ///// Gets the header for the Dbase file.
    ///// </summary>
    //public DbaseHeader DbaseHeader
    //{
    //  get
    //  {
    //    return this._dbfHeader;
    //  }
    //}




    public override bool NextResult()
    {
      throw new InvalidOperationException("shp is single result data source");
    }





    /// <summary>
    /// Implementation specific methods.
    /// </summary>
    /// <param name="strA"></param>
    /// <param name="strB"></param>
    /// <returns></returns>
		private int CultureAwareCompare(string strA, string strB)
		{
			return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
		}
	}
}
